在上一篇分析了DOS Header / DOS Stub / Rich Header
雖然很有趣,但這些區塊在當前Windows系統上不是特別重要
在這篇文章中,我們會開始切入正題,展開NT Headers,了解它的構造
NT Headers是PE檔案相當重要的一個區塊,PE loader會根據這個區段提供的資訊來載入並執行程式
從官方文件提供的架構,我們可以知道NT Headers主要分成三個部分:
// 32 bits
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
而因為Optional Header的關係,所以有32以及64位元兩種定義
// 64 bits
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
系統會根據_WIN64
是否有被定義來決定要使用哪種架構
底下會依序介紹各個部分的組成成員以及這些成員所提供的資訊
首先是Signature
是一個長度為4 bytes的data,用來辨識這是一個PE執行檔
其次是File Header
這是他的資料結構
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
> Structure
把它整理成一個表格
名稱 | 功用 | 備註 |
---|---|---|
Machine | 在什麼CPU架構底下編譯的 | |
NumberOfSections | Section 數量 | |
TimeDateStamp | 檔案創建的時間 | epoch (starts from 1970.1.1 00:00 UTC) |
PointerToSymbolTable | 指向Debug資訊表的指標 | 在Image File中通常為 0 |
NumberOfSymbols | Debug Symbol 數量 | 在Image File中通常為 0 |
SizeOfOptionalHeader | Optional Header 大小 | |
Characteristics | 檔案的額外資訊 |
底下分別是Debug Symbol、時間戳記、Characteristics的額外資訊
> Debug Symbol
PointerToSymbolTable和NumberOfSymbols紀錄的是COFF Debug資訊
用NTSD(Windows NT System Debugger)和KD(Windows NT Kernel Debugger)時會用到
PE Image這兩個欄位的值皆為 0
用$ gcc .\target.c -o test.exe
編譯程式
預設的輸出執行檔會保留這個區域
用$ gcc -s .\target.c -o test.exe
則可以刪除Debug Symbol
也可以用strip.exe來移除Debug Symbol
$ strip test.exe
會直接把整個symbol table都翻掉$ strip --strip-unneeded test.exe
還會保留global symbol> Timestamp
除了COFF File Header之外,在PE檔裡面還有其他地方有存放時間資訊,其中包括
額外的時間資訊,可以提供病毒分析人員更多的線索來臆測攻擊者的心思
🌞SUNBURST就是一個例子
> Characteristics
Characteristics是用bit flag的方式來存放檔案資訊的一個成員
提供的資訊包含:檔案是不是執行檔、記憶體限制、Debug Symbol有沒有被移除
假設欄位中的數值 = 0x226 (0x02 + 0x04 + 0x20 + 0x200)
再來是Optional Header
前九個成員是根據COFF Optional Header的標準所制定的成員
後面的註解是他們所對應變數
typedef struct _IMAGE_OPTIONAL_HEADER64 {
// --- COFF Standard Fields ---
WORD Magic; // unsigned short magic;
BYTE MajorLinkerVersion; // unsigned short vstamp;
BYTE MinorLinkerVersion; // unsigned short vstamp;
DWORD SizeOfCode; // unsigned long tsize;
DWORD SizeOfInitializedData; // unsigned long dsize;
DWORD SizeOfUninitializedData; // unsigned long bsize;
DWORD AddressOfEntryPoint; // unsigned long entry;
DWORD BaseOfCode; // unsigned long text_start;
ULONGLONG ImageBase; // unsigned long data_start;
// --- Windows Specific Fields ---
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
ULONGLONG SizeOfStackReserve;
ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve;
ULONGLONG SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
其餘的成員(從SectionAlignment開始)為Windows自己定義的成員
在檔案被執行時會被PE loader / linker讀取
值得一提的是,根據官方
This similarity between the two file formats isn't happenstance. The goal of this design is to make the linker's job as easy as possible Theoretically, creating an EXE file from a single OBJ should be just a matter of inserting a few tables and modifying a couple of file offsets within the image. With this in mind, you can think of a COFF file as an embryonic PE file. Only a few things are missing or different, so I'll list them here.
可以知道PE Image與COFF相近的結構可以讓linker的工作(obj轉exe)變得容易很多
理論上只要插入幾個tables、更改一些file offsets就可以了
> Structure
一樣把它整理成表格
成員 | 功用 | 備註 |
---|---|---|
Magic | 用來辨識是32還是64位元的執行檔 | PE32 / PE32+ / ROM |
MajorLinkerVersion、MinorLinkerVersion | Linker的版本 | |
SizeOfCode | .text大小 | |
SizeOfInitializedData | .data大小 | |
SizeOfUninitializedData | .bss大小 | |
AddressOfEntryPoint | entry point的RVA | |
BaseOfCode | .text開頭位置的RVA | |
BaseOfData | .data開頭位置的RVA | 只有在32位元的執行檔才有 |
ImageBase | 讀取進記憶體的第一個byte的地址 | |
SectionAlignment | Section的最小單位 | >= FileAlignment |
FileAlignment | 占用磁碟的最小單位 | 64K > value > 512 |
MajorOperatingSystemVersion MinorOperatingSystemVersion | 作業系統版本 | |
MajorImageVersion、MinorImageVersion | 檔案版本 | |
MajorSubsystemVersion MinorSubsystemVersion | 需要的subsystem版本 | |
Win32VersionValue | 保留區 | set to 0 |
SizeOfImage | 檔案大小 | round up to SectionAlignment*n |
SizeOfHeaders | 除DOS Header外之Header大小 | round up to FileAlignment*n |
CheckSum | 辨識執行檔完整性 | 與CRC32相似的自定義算法 |
Subsystem | 執行檔案所需要的subsystem | |
DllCharacteristics | Image file的其他屬性 | ref |
SizeOfStackReserve | 保留給stack的連續虛擬記憶體 | upper limit of stack |
SizeOfStackCommit | 預留給stack的實體記憶體 | ref |
SizeOfHeapReserve | 保留給heap的連續虛擬記憶體 | |
SizeOfHeapCommit | 預留給heap的實體記憶體 | |
LoaderFlags | 保留區 | set to 0 |
NumberOfRvaAndSizes | DataDirectory大小 | |
DataDirectory | IMAGE_DATA_DIRECTORY的array |
前面提到Optional Header有32位元跟64位元兩個版本
用$ nvim -d IMAGE_OPTIONAL_HEADER32 IMAGE_OPTIONAL_HEADER64
可以比較兩個架構的差異
從結果可以觀察到
64位元的架構把限制Stack跟Heap大小的定義從原本的DWORD改成ULONGLONG
同時BaseOfData為了讓位給ImageBase拓展到64 bits而被移除
底下是Alignment跟Subsystem的額外資料
> Alignment
把notepad.exe丟進PE Bear觀察
分別得到
SectionAlignment = 0x1000 (記憶體上的alignment)
FileAlignment = 0x200 (磁碟上的alignment)
看到.text
section實際有放資料的大小為 0x23B6F
在磁碟(SizeOfCode)上的所佔的大小就是 0x23A00 (0x200 * 285)
執行時在記憶體(BaseOfData - BaseOfCode)上佔用的大小就是 0x24000 bytes (0x1000 * 24)
兩個補齊的方式都是用\00做padding
> Subsystem
程式執行時需要的東西
可以猜出程式會用什麼方式呈現(eg. GUI or Console)
我們介紹了NT Headers的架構
並把它拆成三個部分,Signature、File Header、Optional Header
細看資料結構裡面各個member的功能
下一篇,我們會進入Section的領域
0xrick's PE file format analysis
https://0xrick.github.io/win-internals/pe4/
Official Documentation
https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
https://learn.microsoft.com/en-us/cpp/build/reference/symbols?view=msvc-170
Optional Header
https://wiki.osdev.org/PE#Optional_header
COFF
https://en.wikipedia.org/wiki/COFF
COFF Optional Header
http://www.delorie.com/djgpp/doc/coff/opthdr.html
https://0xc0decafe.com/malware-analyst-guide-to-pe-timestamps
PE/COFF ELF formats & terminology
https://stackoverflow.com/questions/2170818/clarification-on-binary-file-pe-coff-elf-formats-terminology
PE loader
https://security.stackexchange.com/questions/24785/where-is-the-pe-loader-in-windows
strip vs. gcc -s
https://stackoverflow.com/questions/1349166/what-is-the-difference-between-gcc-s-and-a-strip-command